<?php
    /**
     * <code>
     * <h1>
     * Examples
     * </h1>
     *
     * <h2>
     * Creating the instance
     * </h2>
     * $config = new configParser("config.cfg"); // create a new instance<br/>
     *
     * <h2>
     * Retrieving the values
     * </h2>
     * $config->getValue("hello"); // get a value<br/>
     * $config->Hello; // We can also retrieve via the arrowhead method<br/>
     * $config("Hello"); // REQUIRES 5.3.0 or greater, we can access via the invoke method
     *
     * <h2>
     * Assigning new values
     * </h2>
     * $config->setValue("newVal", "Some new val"); // assign a new virtual value<br/>
     * $config->Dave = "bob"; // we can also assign via the arrow method
     *
     * <h2>
     * Testing and cleaning up
     * </h2>
     * isset($config->Hello); // test to see if a key exists<br/>
     * unset($config->NewVal) // we can also unset values
     * </code>
     */

	class ConfigParser
    {
		private $handle      = null;
        private $openStatus  = false;
        private $configArray = array();

        /**
         * PURPOSE:
         *   Constructor for the configParser class
         * @param string $StringName - The path to the configuration file.
         */
		public function __construct( $stringName = null )
        {
            if ( $stringName )
            {
                if (! file_exists($stringName) )
                {
                    throw new Exception( sprintf('Incorrect file path: %s', $stringName) );
                }
                $this->open($stringName);
                $this->_parseFile();
                $this->close();
            }
        }

        /**
         * PURPOSE:
         *   Deconstructor for the configParser class
         *   Execute the close function which closes the current handle.
         */
		public function __destruct()
        {
            $this->close();
        }

        /**
         * PURPOSE:
         *   Executed automatically when a user attempts to use the class instance
         *   as a function... e.g. $config(key). Make it so it returns a value
         * @param string $KeyName The name of the config value you're attempting to retrieve
         * @return int|float|string The value from the KeyName
         */
        public function __invoke( $keyName )
        {
            return $this->getValue( $keyName );
        }

        /**
         * PURPOSE:
         *   Executed automatically when a user attempts to set a value via the
         *   arrow attribute to an element which doesn't exist as a member (i.e
         *   a config name and value). Instead of setting a member function,
         *   assign a key to the array with the value)
         * @param string $keyName The key that the value will be assigned to
         * @param int|float|string $keyValue The value that the key will hold
         */
        public function __set( $keyName, $keyValue )
        {
            $this->setValue( $keyName, $keyValue );
        }

        /**
         * PURPOSE:
         *   Executed automatically when an object attempts to retrieve a value
         *   via the arrow attribute which doesn't exist as an instance member,
         *   i.e. attempting to retrieve a value. Return a file from the inbuilt
         *   array.
         * @param string $KeyName The key of which value you want to retrieve
         * @return int|float|string The value which exists at the key of keyName of the private array
         */
        public function __get( $keyName )
        {
            $tempValue = $this->GetValue( $keyName );
            if (isset($tempValue))
                return $tempValue;
            throw new Exception( sprintf( "Value %s does not exist within the config file", $keyName ) );
        }

        /**
         * PURPOSE:
         *   Executed automatically when the isset() function is run on this class.
         *   Attempts to see if the key name is set within the array
         * @param keyName - the key to test if it exists within the array
         * @return boolean value wheteher or not the value is set
         */
        public function __isset( $keyName )
        {
            if ( array_key_exists( $keyName, $this->configArray ) )
                return isset( $this->configArray[$keyName] );
            return false;
        }

        /**
         * PURPOSE:
         *
         *  Executed automatically when the unset() function is run on a class
         *  member, i.e. (unset($configInstance->a)). Unset the value from the
         *  array
         *
         * @param string keyName - the key to unset
         */
        public function __unset( $keyName )
        {
            if ( isset($this->configArray[$keyName]) )
                unset( $this->configArray[$keyName] );
        }

        /**
         * PURPOSE:
         *   Closes the current file handle if we have a file stream currently
         *   active.
         * @return bool Whether or not the close was successful
         */
        public function close()
        {
            if ($this->openStatus)
            {
                // If we have an open handle, close it and set open to false
                $this->openStatus = false;
                return fclose( $this->handle );
            }
            return false;
        }

        /**
         * PURPOSE:
         *   Opens a new file stream in read mode. Thows an error and kills the
         *   execution if file opening fails.
         * @PARAM string $StringPath The file path as a string name.
         */
        public function open( $stringPath )
        {
            $this->handle = fopen($stringPath, 'r');
            if (! $this->handle )
                throw new Exception( sprintf("An unkown error occured attempting to open file:\n%s", $stringPath) );
            //$this->handle     = fopen($stringPath, 'r');
            $this->openStatus = true;
        }

        /**
         * PURPOSE:
         *   Return a value from the configration array
         * @param string $KeyName The name of the key within the configurationArray
         * @return int|float|string The value to which the key coincides. If the key doesn't exist an error is thrown.
         */
        public function getValue( $keyName )
        {
            if ( array_key_exists( $keyName, $this->configArray ) )
            {
                return $this->configArray[$keyName];
            }
            throw new Exception( sprintf( "Key %s does not exist within the configuration array", $keyName ) );
        }

        /**
         * PURPOSE
         *   Allow access to create virtual config files without hard coding a
         *   text file.
         * @param string $KeyName The key name of which you want the config to be named
         * @param int|float|string $KeyValue The value of what you want keyName to hold
         * @return bool Whether or not the set was successful
         */
        public function setValue( $keyName, $keyValue )
        {
            if (! array_key_exists( $keyName, $this->configArray ) )
            {
                // Key does not exist, create it and return true
                $this->configArray[$keyName] = $keyValue;
                return true;
            }
            return false;
        }

        /**
         * Purpose
         *   Return the entirety of the configuration array
         * @RETURN array The configuration array
         */
        public function returnConfigArray()
        {
            return $this->configArray;
        }

        /***************************************************************************
        *
        * Private interace
        *
        * _ParseFile() - iterates through all lines and adds the relevant information to the config array
        *
        ***************************************************************************/

        /**
         * PURPOSE:
         *   Take the current open file, and iterate through line by line,
         *   adding all of the relevant configuration lines
         * @return bool Whether or not the array was successful at adding.
         */
        private function _parseFile()
        {
            if ($this->openStatus)
            {
                // We have a file currently opening, carry on...
                while ($line = fgets($this->handle) )
                {
                    // While we can retrieve a line, trim the whitespace and escape
                    // characters
                    $line = trim($line);
                    if ( substr($line, 0, 2) == "//")
                        // Line is a comment, continue
                        continue;
                    if ( strlen($line) <= 2 )
                        // Line is too short, continue
                        continue;
                    $equalPos = strpos($line, "=");
                    if ($equalPos != -1 && $equalPos != strlen($line) && $equalPos > 0)
                    {
                        // Line is a valid expression, so we need to split it up into
                        // key and value, and add it to the config array

                        // If the line contains a comment, ignore information
                        // after the comment
                        $position = strpos($line, "//");
                        if ( $position !== false  )
                        {
                            $line = substr($line, 0, $position);
                        }

                        $key      = trim( substr($line, 0, $equalPos) );
                        // Strip the Value of whitespace (default chars) With the
                        // addition of the quotation character.
                        $value    = trim( substr($line, $equalPos + 1), " \t\n\r\0\x0B\"'" );
                        if ( is_numeric( $value ) )
                        {
                            if ( strstr($value, ".") )
                            {
                                $value = floatval($value);
                            }
                            else
                            {
                                $value = intval($value);
                            }
                        }
                        $this->configArray[$key] = $value;
                    }
                }
                // Everything executed fine, return true
                return true;
            }
            // We don't have a file open at this moment, return False
            return false;
        }
	}